/***********************************************************************************
Copy right:	    Coffee Tech.
Author:         jiaoyue
Date:           2019.7.31
Description:    本地套接字通信接口组件，注意每个进程只允许有一个服务器，对应每种服务器也只能有一个客户端
***********************************************************************************/

#include <unix_sock.h>
#include <sys/stat.h>

/***************************服务器相关********************************/
static socklen_t sock_len = sizeof(struct sockaddr_un);

/**
 * @brief 初始化服务
 * @param para 通信结构体，传入即可
 * @param sname 服务名称
 * @return 0 -1
 */
int unix_init_server(struct unix_param *para, const char *sname)
{
    assert(NULL != para);
    assert(NULL != sname && strlen(sname) > 0);

    int server_fd;
	struct sockaddr_un server_addr;
    char spath[256];

    sprintf(spath, "/tmp/ipc/unixsock/%s-s", sname);

	server_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
	if (server_fd < 0)
	{
		perror("socket err");
		return -1;
	}

	//指定本地的地址信息
	bzero(&server_addr, sock_len);
	server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, spath);

    //先删除，防止绑定失败
	unlink(server_addr.sun_path);

	if (bind(server_fd, (struct sockaddr *)&server_addr, sock_len) < 0)
	{
		perror("fail to bind server");
		return -1;
	}

    chmod(spath, 0777);
    para->sfd = server_fd;
    para->saddr = server_addr;

	return 0;
}

/**
 * @brief 服务器接收
 * @param para
 * @param buf 缓冲区
 * @param len 缓冲区长度
 * @return 成功：实际接收字节数 失败：-1
 */
ssize_t unix_server_recv(struct unix_param *para, void *buf, size_t len)
{
    assert(NULL != para);
    assert(NULL != buf);

    sock_len = sizeof(struct sockaddr_un);
    return recvfrom(para->sfd, buf, len, 0, (struct sockaddr *)&para->caddr, &sock_len);
}

/**
 * @brief 服务器发送
 * @param para
 * @param buf 缓冲区
 * @param len 缓冲区长度
 * @return 成功：实际发送字节数 失败：-1
 */
ssize_t unix_server_send(const struct unix_param *para, const void *buf, size_t len)
{
    assert(NULL != para);
    assert(NULL != buf);
    return sendto(para->sfd, buf, len, 0, (struct sockaddr *)&para->caddr, sock_len);
}

/***************************客户端相关********************************/

/**
 * @brief 初始化和某个服务通信的unix客户端
 * @param para 通信结构体，传入即可
 * @param cname 客户进程名
 * @param sname 服务进程名
 * @return 0 -1
 */
int unix_init_client(struct unix_param *para, const char *cname, const char *sname)
{
    assert(NULL != para);
    assert(NULL != cname && strlen(cname) > 0);
    assert(NULL != sname && strlen(sname) > 0);
    struct sockaddr_un client_addr, server_addr;

    char cpath[256], spath[256];
    sprintf(spath, "/tmp/ipc/unixsock/%s-s", sname);
    sprintf(cpath, "/tmp/ipc/unixsock/%s-c-%s", sname, cname);

	int client_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
	if (client_fd < 0)
	{
		perror("socket err");
		return -1;
	}

	//指定服务器的地址信息
	bzero(&server_addr, sock_len);
	server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, spath);

	//绑定客户端地址
	bzero(&client_addr, sock_len);
	client_addr.sun_family = AF_UNIX;
    strcpy(client_addr.sun_path, cpath);

	//先删除，防止绑定失败
	unlink(client_addr.sun_path);
	if (bind(client_fd, (struct sockaddr *)&client_addr, sock_len) < 0)
	{
		perror("fail to bind client");
		return -1;
	}

    para->cfd = client_fd;
    para->caddr = client_addr;
    para->saddr = server_addr;

    chmod(cpath, 0777);
    return 0;
}

/**
 * @brief 往某个服务发送内容
 * @param para
 * @param buf
 * @param len
 * @return
 */
ssize_t unix_client_send(const struct unix_param *para, const void *buf, size_t len)
{
    assert(NULL != para);
    assert(NULL != buf);
    return sendto(para->cfd, buf, len, 0, (struct sockaddr *)&para->saddr, sock_len);
}

/**
 * @brief 往某个服务非阻塞发送
 * @param para
 * @param buf
 * @param len
 * @return
 */
ssize_t unix_client_send_nowait(const struct unix_param *para, const void *buf, size_t len)
{
    assert(NULL != para);
    assert(NULL != buf);
    return sendto(para->cfd, buf, len, MSG_DONTWAIT, (struct sockaddr *)&para->saddr, sock_len);
}

/**
 * @brief 接收某个服务的消息
 * @param para
 * @param buf
 * @param len
 * @param timeout 接收超时时间，传入NULL为阻塞接收
 * @return <0：接收出错 =0：接收超时 >0：接收到字节数
 */
ssize_t unix_client_recv(const struct unix_param *para, void *buf, size_t len, struct timeval *timeout)
{
    assert(NULL != para);
    assert(NULL != buf);
    int cfd = para->cfd;
    fd_set rdfs;
    FD_ZERO(&rdfs);
    FD_SET(cfd, &rdfs);
    int ret = select(cfd+1, &rdfs, NULL, NULL, timeout);
    if(ret > 0)  //代表有数据
    {
        if(FD_ISSET(cfd, &rdfs))
        {
            return recvfrom(para->cfd, buf, len, 0, NULL, NULL);
        }
        else
        {
            return -1;
        }
    }
    else
    {
        return ret;  //0为超时
    }
}
